<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Flutter 2 商城App实战指南（支持Null safety） | 青山依旧的网络日记</title>
    <meta name="description" content="记录web前端那些事">
    <link rel="stylesheet" href="/note/assets/style.44cf3921.css">
    <link rel="modulepreload" href="/note/assets/chunks/VPAlgoliaSearchBox.e8a13dea.js">
    <link rel="modulepreload" href="/note/assets/chunks/ui-custom.8a747abb.js">
    <link rel="modulepreload" href="/note/assets/app.6518bc14.js">
    <link rel="modulepreload" href="/note/assets/guide_flutter-mall.md.053d420a.lean.js">
    
    <meta name="keywords" content="jsdawn,青山依旧,网络日记,笔记,web前端,js,vue,vite">
  <link rel="icon" href="/note/logo.svg">
  <script>(()=>{const e=localStorage.getItem("vitepress-theme-appearance"),a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-c66ff1de><!--[--><!--]--><!--[--><span tabindex="-1" data-v-954d4690></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-954d4690> Skip to content </a><!--]--><!----><header class="VPNav" data-v-c66ff1de data-v-f9fa4498><div class="VPNavBar has-sidebar" data-v-f9fa4498 data-v-d7d748c2><div class="container" data-v-d7d748c2><div class="VPNavBarTitle has-sidebar" data-v-d7d748c2 data-v-01b9aac6><a class="title" href="/note/" data-v-01b9aac6><!--[--><img class="VPImage logo" src="/note/logo.svg" data-v-67b84fc0><!--]--><!--[-->青山依旧的网络日记<!--]--></a></div><div class="content" data-v-d7d748c2><div class="VPNavBarSearch search" data-v-d7d748c2><div id="docsearch"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key">Meta</kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-d7d748c2 data-v-654db621><span id="main-nav-aria-label" class="visually-hidden" data-v-654db621>Main Navigation</span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/note/guide/vue3-upgrade.html" data-v-654db621 data-v-7561f494 data-v-32407edf><!--[-->前端指南<!--]--><!----></a><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-654db621 data-v-bf49c558><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-bf49c558><span class="text" data-v-bf49c558><!----> 踩坑心得 <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-bf49c558><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-bf49c558><div class="VPMenu" data-v-bf49c558 data-v-0f64c8d2><div class="items" data-v-0f64c8d2><!--[--><!--[--><div class="VPMenuLink" data-v-0f64c8d2 data-v-087d3aaa><a class="VPLink link" href="/note/feelings/el-date-picker.html" data-v-087d3aaa data-v-32407edf><!--[-->Element UI 系列<!--]--><!----></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-0f64c8d2 data-v-087d3aaa><a class="VPLink link" href="/note/feelings/a-upload.html" data-v-087d3aaa data-v-32407edf><!--[-->Ant design vue 系列<!--]--><!----></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-0f64c8d2 data-v-087d3aaa><a class="VPLink link" href="/note/feelings/echarts-wordcloud.html" data-v-087d3aaa data-v-32407edf><!--[-->Echarts 系列<!--]--><!----></a></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/note/node/git-npm.html" data-v-654db621 data-v-7561f494 data-v-32407edf><!--[-->Git/Node<!--]--><!----></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/note/tale/hexo-create.html" data-v-654db621 data-v-7561f494 data-v-32407edf><!--[-->青山物语<!--]--><!----></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-d7d748c2 data-v-79082d9f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" aria-label="toggle dark mode" data-v-79082d9f data-v-47f2e383 data-v-9ae3dd98><span class="check" data-v-9ae3dd98><span class="icon" data-v-9ae3dd98><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-47f2e383><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-47f2e383><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-d7d748c2 data-v-6745ff8f data-v-b7a19e6a><!--[--><a class="VPSocialLink" href="https://github.com/jsdawn" title="github" target="_blank" rel="noopener noreferrer" data-v-b7a19e6a data-v-205dd35e><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-205dd35e><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path></svg><span class="visually-hidden" data-v-205dd35e>github</span></a><a class="VPSocialLink" href="https://juejin.cn/user/2101921962267079/posts" title="slack" target="_blank" rel="noopener noreferrer" data-v-b7a19e6a data-v-205dd35e><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-205dd35e><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"></path></svg><span class="visually-hidden" data-v-205dd35e>slack</span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-d7d748c2 data-v-7bb88228 data-v-bf49c558><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-bf49c558><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-bf49c558><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-bf49c558><div class="VPMenu" data-v-bf49c558 data-v-0f64c8d2><!----><!--[--><!--[--><!----><div class="group" data-v-7bb88228><div class="item appearance" data-v-7bb88228><p class="label" data-v-7bb88228>Appearance</p><div class="appearance-action" data-v-7bb88228><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" aria-label="toggle dark mode" data-v-7bb88228 data-v-47f2e383 data-v-9ae3dd98><span class="check" data-v-9ae3dd98><span class="icon" data-v-9ae3dd98><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-47f2e383><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-47f2e383><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-7bb88228><div class="item social-links" data-v-7bb88228><div class="VPSocialLinks social-links-list" data-v-7bb88228 data-v-b7a19e6a><!--[--><a class="VPSocialLink" href="https://github.com/jsdawn" title="github" target="_blank" rel="noopener noreferrer" data-v-b7a19e6a data-v-205dd35e><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-205dd35e><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path></svg><span class="visually-hidden" data-v-205dd35e>github</span></a><a class="VPSocialLink" href="https://juejin.cn/user/2101921962267079/posts" title="slack" target="_blank" rel="noopener noreferrer" data-v-b7a19e6a data-v-205dd35e><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-205dd35e><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"></path></svg><span class="visually-hidden" data-v-205dd35e>slack</span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-d7d748c2 data-v-2379f326><span class="container" data-v-2379f326><span class="top" data-v-2379f326></span><span class="middle" data-v-2379f326></span><span class="bottom" data-v-2379f326></span></span></button></div></div></div><!----></header><div class="VPLocalNav" data-v-c66ff1de data-v-2d7b81c3><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-2d7b81c3><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-2d7b81c3><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-2d7b81c3>Menu</span></button><a class="top-link" href="#" data-v-2d7b81c3> Return to top </a></div><aside class="VPSidebar" data-v-c66ff1de data-v-0970777a><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-0970777a><span class="visually-hidden" id="sidebar-aria-label" data-v-0970777a> Sidebar Navigation </span><!--[--><div class="group" data-v-0970777a><section class="VPSidebarGroup collapsible" data-v-0970777a data-v-5973b055><div class="title" role="button" data-v-5973b055><h2 class="title-text" data-v-5973b055>前端指南</h2><div class="action" data-v-5973b055><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 24 24" class="icon minus" data-v-5973b055><path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2zM20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"></path><path d="M16,11H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h8c0.6,0,1-0.4,1-1S16.6,11,16,11z"></path></svg><svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="icon plus" data-v-5973b055><path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2z M20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"></path><path d="M16,11h-3V8c0-0.6-0.4-1-1-1s-1,0.4-1,1v3H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h3v3c0,0.6,0.4,1,1,1s1-0.4,1-1v-3h3c0.6,0,1-0.4,1-1S16.6,11,16,11z"></path></svg></div></div><div class="items" data-v-5973b055><!--[--><a class="VPLink link" href="/note/guide/vue3-upgrade.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Vue3.0 项目升级到 Vue3.2</span><!--]--><!----></a><a class="VPLink link" href="/note/guide/vue3-tinymce.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Vue3 富文本编辑器</span><!--]--><!----></a><a class="VPLink link" href="/note/guide/vue3-i18n.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>vue3 国际化(i18n)</span><!--]--><!----></a><a class="VPLink link active" href="/note/guide/flutter-mall.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Flutter 2 商城App实战指南</span><!--]--><!----></a><a class="VPLink link" href="/note/guide/flutter-weiget.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Flutter 布局相关</span><!--]--><!----></a><a class="VPLink link" href="/note/guide/flutter-const.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Flutter 语法相关</span><!--]--><!----></a><a class="VPLink link" href="/note/guide/docker-compose.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Docker 部署 Node 项目</span><!--]--><!----></a><a class="VPLink link" href="/note/guide/docker-github.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Docker GitHub 集成工作流</span><!--]--><!----></a><!--]--></div></section></div><div class="group" data-v-0970777a><section class="VPSidebarGroup collapsible" data-v-0970777a data-v-5973b055><div class="title" role="button" data-v-5973b055><h2 class="title-text" data-v-5973b055>踩坑心得</h2><div class="action" data-v-5973b055><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 24 24" class="icon minus" data-v-5973b055><path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2zM20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"></path><path d="M16,11H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h8c0.6,0,1-0.4,1-1S16.6,11,16,11z"></path></svg><svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="icon plus" data-v-5973b055><path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2z M20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"></path><path d="M16,11h-3V8c0-0.6-0.4-1-1-1s-1,0.4-1,1v3H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h3v3c0,0.6,0.4,1,1,1s1-0.4,1-1v-3h3c0.6,0,1-0.4,1-1S16.6,11,16,11z"></path></svg></div></div><div class="items" data-v-5973b055><!--[--><a class="VPLink link" href="/note/feelings/vue3-waterfall-flow.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Vue3 瀑布流列表组件封装</span><!--]--><!----></a><a class="VPLink link" href="/note/feelings/js-lazy-load.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>JS 图片懒加载</span><!--]--><!----></a><a class="VPLink link" href="/note/feelings/el-date-picker.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Element 日期组件范围选择</span><!--]--><!----></a><a class="VPLink link" href="/note/feelings/a-upload.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Antd vue Upload 自定义上传</span><!--]--><!----></a><a class="VPLink link" href="/note/feelings/echarts-wordcloud.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Echarts 词云图</span><!--]--><!----></a><!--]--></div></section></div><div class="group" data-v-0970777a><section class="VPSidebarGroup collapsible" data-v-0970777a data-v-5973b055><div class="title" role="button" data-v-5973b055><h2 class="title-text" data-v-5973b055>Git / Node / Npm</h2><div class="action" data-v-5973b055><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 24 24" class="icon minus" data-v-5973b055><path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2zM20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"></path><path d="M16,11H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h8c0.6,0,1-0.4,1-1S16.6,11,16,11z"></path></svg><svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="icon plus" data-v-5973b055><path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2z M20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"></path><path d="M16,11h-3V8c0-0.6-0.4-1-1-1s-1,0.4-1,1v3H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h3v3c0,0.6,0.4,1,1,1s1-0.4,1-1v-3h3c0.6,0,1-0.4,1-1S16.6,11,16,11z"></path></svg></div></div><div class="items" data-v-5973b055><!--[--><a class="VPLink link" href="/note/node/git-npm.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Git &amp; Npm 常用指令</span><!--]--><!----></a><!--]--></div></section></div><div class="group" data-v-0970777a><section class="VPSidebarGroup collapsible" data-v-0970777a data-v-5973b055><div class="title" role="button" data-v-5973b055><h2 class="title-text" data-v-5973b055>青山物语</h2><div class="action" data-v-5973b055><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 24 24" class="icon minus" data-v-5973b055><path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2zM20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"></path><path d="M16,11H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h8c0.6,0,1-0.4,1-1S16.6,11,16,11z"></path></svg><svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="icon plus" data-v-5973b055><path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2z M20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"></path><path d="M16,11h-3V8c0-0.6-0.4-1-1-1s-1,0.4-1,1v3H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h3v3c0,0.6,0.4,1,1,1s1-0.4,1-1v-3h3c0.6,0,1-0.4,1-1S16.6,11,16,11z"></path></svg></div></div><div class="items" data-v-5973b055><!--[--><a class="VPLink link" href="/note/tale/hualang.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>🏞 青山画廊</span><!--]--><!----></a><a class="VPLink link" href="/note/tale/hexo-create.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>Hexo 博客搭建简易教程</span><!--]--><!----></a><a class="VPLink link" href="/note/tale/p1-zongjie.html" data-v-5973b055 data-v-ded4328e data-v-32407edf><!--[--><span class="link-text" data-v-ded4328e>P1 攻城狮的江湖总结</span><!--]--><!----></a><!--]--></div></section></div><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-c66ff1de data-v-580a4f65><div class="VPDoc has-sidebar" data-v-580a4f65 data-v-594e0a10><div class="container" data-v-594e0a10><div class="aside" data-v-594e0a10><div class="aside-curtain" data-v-594e0a10></div><div class="aside-container" data-v-594e0a10><div class="aside-content" data-v-594e0a10><div class="VPDocAside" data-v-594e0a10 data-v-6d380436><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline has-outline" data-v-6d380436 data-v-55303134><div class="content" data-v-55303134><div class="outline-marker" data-v-55303134></div><div class="outline-title" data-v-55303134>﹌﹋文章目录﹋﹌</div><nav aria-labelledby="doc-outline-aria-label" data-v-55303134><span class="visually-hidden" id="doc-outline-aria-label" data-v-55303134> Table of Contents for current page </span><ul class="root" data-v-55303134><!--[--><li style="" data-v-55303134><a class="outline-link" href="#阅读文章你将收获" data-v-55303134>阅读文章你将收获</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#flutter-开发环境搭建" data-v-55303134>Flutter 开发环境搭建</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#项目创建与运行" data-v-55303134>项目创建与运行</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#自定义项目主题" data-v-55303134>自定义项目主题</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#flutter-路由配置" data-v-55303134>Flutter 路由配置</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#网络请求与数据模型" data-v-55303134>网络请求与数据模型</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#svg-图标的使用" data-v-55303134>SVG 图标的使用</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#瀑布流布局" data-v-55303134>瀑布流布局</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#购物车功能" data-v-55303134>购物车功能</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#项目-app-打包" data-v-55303134>项目 App 打包</a><!----></li><li style="" data-v-55303134><a class="outline-link" href="#结语" data-v-55303134>结语</a><!----></li><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-6d380436></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-594e0a10><div class="content-container" data-v-594e0a10><!--[--><!--]--><main class="main" data-v-594e0a10><div style="position:relative;" class="vp-doc _note_guide_flutter-mall" data-v-594e0a10><div><h1 id="flutter-2-商城-app-实战指南（支持-null-safety）" tabindex="-1">Flutter 2 商城 App 实战指南（支持 Null safety） <a class="header-anchor" href="#flutter-2-商城-app-实战指南（支持-null-safety）" aria-hidden="true">#</a></h1><blockquote><p><code>flutter</code> 从零开发一个电子商城 APP，一套代码库可以高效构建多平台精美应用（包括安卓 App/苹果 IOSApp/苹果 mac 应用/windows 应用）</p></blockquote><h2 id="阅读文章你将收获" tabindex="-1">阅读文章你将收获 <a class="header-anchor" href="#阅读文章你将收获" aria-hidden="true">#</a></h2><p>文章主要针对 flutter 开发实战，详细描述 <strong>主要知识点</strong> 在实战中的运用。希望大家在实战中势如破竹，得心应手。（下图为本项目所有知识点梳理）</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9125d56f8ac446ebb0474368ee63241f~tplv-k3u1fbpfcp-watermark.image?" alt="商城思维导图.jpg"></p><ul><li><strong>项目自定义主题</strong>：创建自定义主题类，设定项目 UI 颜色，自定义各类文字样式，方便统一项目 UI，灵活布局。</li><li><strong>Flutter 路由</strong>：项目不可或缺的部分，各页面之间跳转。</li><li><strong>SVG 图标的使用</strong>：使用精美的 svg 图标，多一份热爱，多一份色彩。</li><li><strong>瀑布流布局</strong>：商品列表使用瀑布流布局，完美协调列表项。</li><li><strong>Flutter 数据模型</strong>：让数据可用性更好，JSON 与数据模型转换，以便在项目中使用各类数据。</li><li><strong>购物车功能</strong>：买不买就看购物车爽不爽。购物车增删改查的实现，购物车商品本地持久化存储。</li><li><strong>项目 App 打包</strong>：应用打包和其他注意事项。</li><li><strong>一个完整的商城 App 开发能力</strong></li></ul><h2 id="flutter-开发环境搭建" tabindex="-1">Flutter 开发环境搭建 <a class="header-anchor" href="#flutter-开发环境搭建" aria-hidden="true">#</a></h2><p>项目使用 <code>Flutter 2.10.2</code> 版本，您可使用 &gt;=2.x 的版本：</p><div class="language-json"><span class="copy"></span><pre><code><span class="line"><span style="color:#A6ACCD;">Flutter </span><span style="color:#F78C6C;">2.10</span><span style="color:#A6ACCD;">.</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> • channel stable • https:</span><span style="color:#676E95;font-style:italic;">//github.com/flutter/flutter.git</span></span>
<span class="line"><span style="color:#A6ACCD;">Framework • revision </span><span style="color:#F78C6C;">097</span><span style="color:#A6ACCD;">d</span><span style="color:#F78C6C;">3313</span><span style="color:#A6ACCD;">d</span><span style="color:#F78C6C;">8</span></span>
<span class="line"><span style="color:#A6ACCD;">Engine • revision a</span><span style="color:#F78C6C;">83</span><span style="color:#A6ACCD;">ed</span><span style="color:#F78C6C;">0e5</span><span style="color:#A6ACCD;">e</span><span style="color:#F78C6C;">3</span></span>
<span class="line"><span style="color:#A6ACCD;">Tools • Dart </span><span style="color:#F78C6C;">2.16</span><span style="color:#A6ACCD;">.</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> • DevTools </span><span style="color:#F78C6C;">2.9</span><span style="color:#A6ACCD;">.</span><span style="color:#F78C6C;">2</span></span>
<span class="line"></span></code></pre></div><ol><li><p>选择要安装 Flutter 的操作系统，并按照官方操作流程进行 Flutter 环境的安装及验证：</p><ul><li>Flutter 官网：<a href="https://docs.flutter.dev/get-started/install" target="_blank" rel="noopener noreferrer">https://docs.flutter.dev/get-started/install</a></li><li>Flutter 中文官网：<a href="https://flutter.cn/docs/get-started/install" target="_blank" rel="noopener noreferrer">https://flutter.cn/docs/get-started/install</a></li></ul></li><li><p>在 VS Code 中开发（个人建议使用轻量的 vscode 开发）</p><ul><li>配置编辑器：<a href="https://flutter.cn/docs/get-started/editor?tab=vscode" target="_blank" rel="noopener noreferrer">https://flutter.cn/docs/get-started/editor?tab=vscode</a></li></ul></li></ol><h2 id="项目创建与运行" tabindex="-1">项目创建与运行 <a class="header-anchor" href="#项目创建与运行" aria-hidden="true">#</a></h2><p><strong>项目创建：</strong></p><p>在您的工作目录中使用命令行工具：</p><div class="language-sh"><span class="copy"></span><pre><code><span class="line"><span style="color:#A6ACCD;">flutter create flutter_demo</span></span>
<span class="line"></span></code></pre></div><p><code>flutter_demo</code> 是项目名称（替换为你的项目名称），官方建议文件名称以下划线命名方式。</p><p><strong>项目目录结构：</strong></p><div class="language-js"><span class="copy"></span><pre><code><span class="line"><span style="color:#A6ACCD;">├─assets             </span><span style="color:#676E95;font-style:italic;">// 静态文件</span></span>
<span class="line"><span style="color:#A6ACCD;">├─lib                </span><span style="color:#676E95;font-style:italic;">// 主要工作目录，编写大部分代码放在这里</span></span>
<span class="line"><span style="color:#A6ACCD;">│  ├─apis            </span><span style="color:#676E95;font-style:italic;">// api 文件</span></span>
<span class="line"><span style="color:#A6ACCD;">│  ├─components      </span><span style="color:#676E95;font-style:italic;">// 公共组件</span></span>
<span class="line"><span style="color:#A6ACCD;">│  ├─models          </span><span style="color:#676E95;font-style:italic;">// 数据模型</span></span>
<span class="line"><span style="color:#A6ACCD;">│  ├─router          </span><span style="color:#676E95;font-style:italic;">// 项目路由</span></span>
<span class="line"><span style="color:#A6ACCD;">│  ├─utils           </span><span style="color:#676E95;font-style:italic;">// 工具类方法</span></span>
<span class="line"><span style="color:#A6ACCD;">│  ├─views           </span><span style="color:#676E95;font-style:italic;">// 商城页面UI</span></span>
<span class="line"><span style="color:#A6ACCD;">│  ├─widgets         </span><span style="color:#676E95;font-style:italic;">// 业务相关组件</span></span>
<span class="line"><span style="color:#A6ACCD;">│  ├─app_theme</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">dart  </span><span style="color:#676E95;font-style:italic;">// 自定义主题文件</span></span>
<span class="line"><span style="color:#A6ACCD;">│  └─mian</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">js         </span><span style="color:#676E95;font-style:italic;">// 项目入口文件</span></span>
<span class="line"><span style="color:#A6ACCD;">├─android            </span><span style="color:#676E95;font-style:italic;">// android 编译目录</span></span>
<span class="line"><span style="color:#A6ACCD;">├─ios                </span><span style="color:#676E95;font-style:italic;">// ios 编译目录</span></span>
<span class="line"><span style="color:#A6ACCD;">├─pubspec</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">yaml       </span><span style="color:#676E95;font-style:italic;">// 项目配置文件</span></span>
<span class="line"></span></code></pre></div><p><strong>运行项目：</strong></p><ul><li><p>准备虚拟机</p><ul><li>mac 电脑可在终端执行 <code>$ open -a Simulator</code> 打开 iphone 虚拟机</li><li>安装有 Android Studio 的同学可以在 Android Studio 中打开 安卓虚拟机</li><li>无虚拟机，也可以使用 web 浏览器模式运行开发</li></ul></li><li><p>在终端执行指令 <code>flutter run</code> 运行项目</p></li></ul><h2 id="自定义项目主题" tabindex="-1">自定义项目主题 <a class="header-anchor" href="#自定义项目主题" aria-hidden="true">#</a></h2><p>创建自定义主题类，设定项目 UI 颜色，自定义各类文字样式，方便统一项目 UI，灵活布局。</p><p><strong>编写主题类：</strong></p><p>创建文件 <code>lib/app_theme.dart</code>, 创建 <code>AppTheme</code>类并设计主题颜色和文字样式</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:flutter/material.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AppTheme</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  AppTheme</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">_</span><span style="color:#A6ACCD;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 颜色设置</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> const Color </span><span style="color:#F07178;">notWhite</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">Color</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">0xFFECF0EF</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> const Color </span><span style="color:#F07178;">nearlyWhite</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">Color</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">0xFFFFFFFF</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 文字样式</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> const TextTheme </span><span style="color:#F07178;">textTheme</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">TextTheme</span><span style="color:#A6ACCD;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">    headline4: display1</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    headline5: headline</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    headline6: title</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    subtitle2: subtitle</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    bodyText1: body1</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    bodyText2: body2</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// bodyText2: flutter默认文字样式</span></span>
<span class="line"><span style="color:#A6ACCD;">    caption: caption</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  )</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> const TextStyle </span><span style="color:#F07178;">display1</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">TextStyle</span><span style="color:#A6ACCD;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">    fontFamily: fontName</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    fontWeight: FontWeight</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">bold</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    fontSize: </span><span style="color:#F78C6C;">36</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    letterSpacing: </span><span style="color:#F78C6C;">0.5</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    height: </span><span style="color:#F78C6C;">0.9</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    color: darkerText</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  )</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p><strong>引入并使用主题：</strong> <code>main.dart</code> 入口文件中引入主题类并使用</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:shopping_mall/app_theme.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#A6ACCD;">override</span></span>
<span class="line"><span style="color:#A6ACCD;">Widget </span><span style="color:#82AAFF;">build</span><span style="color:#A6ACCD;">(BuildContext context) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">MaterialApp</span><span style="color:#F07178;">(</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">title</span><span style="color:#F07178;">: </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">青山商城</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">debugShowCheckedModeBanner</span><span style="color:#F07178;">: </span><span style="color:#FF9CAC;">false</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">      </span><span style="color:#676E95;font-style:italic;">// 主题配置</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">theme</span><span style="color:#F07178;">: </span><span style="color:#82AAFF;">ThemeData</span><span style="color:#F07178;">(</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">primarySwatch</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">Colors</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">teal</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">fontFamily</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">AppTheme</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">fontName</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#676E95;font-style:italic;">// 默认字体</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">textTheme</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">AppTheme</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">textTheme</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#676E95;font-style:italic;">// 文字主题</span></span>
<span class="line"><span style="color:#F07178;">      )</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">onGenerateRoute</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">onGenerateRoute</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">initialRoute</span><span style="color:#F07178;">: </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">/</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">home</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">const</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">AppHomeScreen</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">    )</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p><strong>设置状态栏样式：</strong> <code>main.dart</code> 入口文件中使用 <code>setSystemUIOverlayStyle</code></p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#A6ACCD;">() async </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">WidgetsFlutterBinding</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">ensureInitialized</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 设置状态栏</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">SystemChrome</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setSystemUIOverlayStyle</span><span style="color:#F07178;">(</span><span style="color:#82AAFF;">SystemUiOverlayStyle</span><span style="color:#F07178;">(</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 顶部状态栏颜色</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">statusBarColor</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">Colors</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">transparent</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 顶部状态栏图标的亮度</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">statusBarIconBrightness</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">Brightness</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">dark</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 顶部状态栏的亮度</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">statusBarBrightness</span><span style="color:#F07178;">:</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#89DDFF;">!</span><span style="color:#A6ACCD;">kIsWeb</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&amp;&amp;</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">Platform</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">isAndroid</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">?</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">Brightness</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">dark</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">:</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">Brightness</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">light</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 系统底部导航栏的颜色</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">systemNavigationBarColor</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">Colors</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">white</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 系统底部导航栏分割线颜色</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">systemNavigationBarDividerColor</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">Colors</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">transparent</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 系统导航栏图标的亮度</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">systemNavigationBarIconBrightness</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">Brightness</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">dark</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">  ))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 设置设备方向</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;font-style:italic;">await</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">SystemChrome</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setPreferredOrientations</span><span style="color:#F07178;">(&lt;</span><span style="color:#FFCB6B;">DeviceOrientation</span><span style="color:#F07178;">&gt;[</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">DeviceOrientation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">portraitUp</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">DeviceOrientation</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">portraitDown</span></span>
<span class="line"><span style="color:#F07178;">  ])</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">then</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">_</span><span style="color:#89DDFF;">)</span><span style="color:#F07178;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">runApp</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">const</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">ProviderScope</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">child</span><span style="color:#F07178;">: </span><span style="color:#82AAFF;">MyApp</span><span style="color:#F07178;">())))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h2 id="flutter-路由配置" tabindex="-1">Flutter 路由配置 <a class="header-anchor" href="#flutter-路由配置" aria-hidden="true">#</a></h2><p><strong>新建 <code>lib/routers/routes.dart</code> 文件：</strong></p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:flutter/material.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:shopping_mall/views/detail/detail_index.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:shopping_mall/views/goods/goods_list_index.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">/// 路由生成器</span></span>
<span class="line"><span style="color:#A6ACCD;">Route </span><span style="color:#82AAFF;">onGenerateRoute</span><span style="color:#A6ACCD;">(RouteSettings settings) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;font-style:italic;">switch</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">settings</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">name</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">case</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">/detail</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">MaterialPageRoute</span><span style="color:#F07178;">(</span></span>
<span class="line"><span style="color:#F07178;">          </span><span style="color:#A6ACCD;">builder</span><span style="color:#F07178;">: </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">_</span><span style="color:#89DDFF;">)</span><span style="color:#F07178;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">const</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">DetailIndex</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">settings</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">settings</span><span style="color:#F07178;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">case</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">/goods</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">MaterialPageRoute</span><span style="color:#F07178;">(</span></span>
<span class="line"><span style="color:#F07178;">          </span><span style="color:#A6ACCD;">builder</span><span style="color:#F07178;">: </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">_</span><span style="color:#89DDFF;">)</span><span style="color:#F07178;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">const</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">GoodsListIndex</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">settings</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">settings</span><span style="color:#F07178;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">default</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p><strong><code>mian.dart</code> 中使用路由生成器属性 <code>onGenerateRoute</code>:</strong></p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MaterialApp</span><span style="color:#A6ACCD;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">  title: </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">青山商城</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 路由生成器</span></span>
<span class="line"><span style="color:#A6ACCD;">  onGenerateRoute: onGenerateRoute</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 初始路由路径</span></span>
<span class="line"><span style="color:#A6ACCD;">  initialRoute: </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">/</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  home: const </span><span style="color:#82AAFF;">AppHomeScreen</span><span style="color:#A6ACCD;">()</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span></code></pre></div><p><strong>路由跳转与传参：</strong></p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#676E95;font-style:italic;">// 跳转到详情页，传参商品id</span></span>
<span class="line"><span style="color:#A6ACCD;">Navigator</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">of</span><span style="color:#A6ACCD;">(context)</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">pushNamed</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">/detail</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> arguments: </span><span style="color:#89DDFF;">{</span><span style="color:#89DDFF;">&#39;</span><span style="color:#F07178;">id</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> goods</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 跳转到首页，传参商品tab，并移除历史路由</span></span>
<span class="line"><span style="color:#A6ACCD;">Navigator</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">of</span><span style="color:#A6ACCD;">(context)</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">pushNamedAndRemoveUntil</span><span style="color:#A6ACCD;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">/</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">route</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#FF9CAC;">false</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    arguments: </span><span style="color:#89DDFF;">{</span><span style="color:#89DDFF;">&#39;</span><span style="color:#F07178;">tab</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 页面获取传过来的参数</span></span>
<span class="line"><span style="color:#FFCB6B;">Map</span><span style="color:#A6ACCD;"> args </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> ModalRoute</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">of</span><span style="color:#A6ACCD;">(context)</span><span style="color:#89DDFF;">?.</span><span style="color:#A6ACCD;">settings</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">arguments </span><span style="color:#89DDFF;font-style:italic;">as</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Map</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">int id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> args[</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">]</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span></code></pre></div><h2 id="网络请求与数据模型" tabindex="-1">网络请求与数据模型 <a class="header-anchor" href="#网络请求与数据模型" aria-hidden="true">#</a></h2><h3 id="http-请求" tabindex="-1">Http 请求 <a class="header-anchor" href="#http-请求" aria-hidden="true">#</a></h3><p>使用 <code>dio</code> 库，dio 封装查看: <a href="https://github.com/JunAILiang/flutter_dio_util" target="_blank" rel="noopener noreferrer">flutter_dio_util</a></p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#676E95;font-style:italic;">// 简单使用</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:dio/dio.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#A6ACCD;">() async </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">var</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">dio</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">Dio</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">final</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">response</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;font-style:italic;">await</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">dio</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">https://google.com</span><span style="color:#89DDFF;">&#39;</span><span style="color:#F07178;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#82AAFF;">print</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">response</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">data</span><span style="color:#F07178;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// response.data 这里返回的是json字符串，可使用 data[&#39;name&#39;] 获取值</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 要想像 Object 一样使用 data.name 获取。需要转换为Dart模型（Json to Dart）</span></span>
<span class="line"></span></code></pre></div><h3 id="json-to-dart" tabindex="-1">Json to Dart <a class="header-anchor" href="#json-to-dart" aria-hidden="true">#</a></h3><p>假如有如下 <code>Person</code> Json 数据</p><div class="language-json"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">jsdawn</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">age</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">18</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">gender</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;&quot;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p>使用 <a href="https://app.quicktype.io/" target="_blank" rel="noopener noreferrer">quicktype</a> 将 Json 快速转换 为 Dart 模型 <code>person.dart</code></p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#676E95;font-style:italic;">// To parse this JSON data, do</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//     final person = personFromJson(jsonString);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">dart:convert</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 字符串转为`Person`模型</span></span>
<span class="line"><span style="color:#A6ACCD;">Person </span><span style="color:#82AAFF;">personFromJson</span><span style="color:#A6ACCD;">(</span><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> str) </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> Person</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">fromJson</span><span style="color:#A6ACCD;">(json</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">decode</span><span style="color:#A6ACCD;">(str))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// `Person`数据转为字符串</span></span>
<span class="line"><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">personToJson</span><span style="color:#A6ACCD;">(Person data) </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> json</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">encode</span><span style="color:#A6ACCD;">(data</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">toJson</span><span style="color:#A6ACCD;">())</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Person</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">Person</span><span style="color:#89DDFF;">({</span></span>
<span class="line"><span style="color:#A6ACCD;">        this.name</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        this.age</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        this.gender</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">name</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    int </span><span style="color:#F07178;">age</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">gender</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// json中字段不可少，否则需要判空：json[&quot;name&quot;] ?? &quot;&quot;</span></span>
<span class="line"><span style="color:#A6ACCD;">    factory Person</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">fromJson</span><span style="color:#A6ACCD;">(</span><span style="color:#FFCB6B;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> dynamic</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> json) </span><span style="color:#89DDFF;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Person</span><span style="color:#A6ACCD;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        name: json[</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">]</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        age: json[</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">]</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        gender: json[</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">gender</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">]</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    )</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#FFCB6B;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> dynamic</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">toJson</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">name</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">age</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">gender</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">gender</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">};</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p>在 flutter 中使用：</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#676E95;font-style:italic;">// 这里 DefaultAssetBundle 的 loadString 加载 Json 文件，模拟接口调用</span></span>
<span class="line"><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> jsonString </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">await</span><span style="color:#A6ACCD;"> DefaultAssetBundle</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">of</span><span style="color:#A6ACCD;">(context)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">loadString</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">assets/json/data.json</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 字符串转为 Dart Model</span></span>
<span class="line"><span style="color:#A6ACCD;">final person </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">personFromJson</span><span style="color:#A6ACCD;">(jsonString)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#82AAFF;">print</span><span style="color:#A6ACCD;">(person</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">name)</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// jsdawn</span></span>
<span class="line"></span></code></pre></div><h2 id="svg-图标的使用" tabindex="-1">SVG 图标的使用 <a class="header-anchor" href="#svg-图标的使用" aria-hidden="true">#</a></h2><p>使用精美的 svg 图标，多一份热爱，多一份色彩。</p><p><strong>引入图标资源：</strong></p><p>从  <a href="https://www.iconfont.cn/" target="_blank" rel="noopener noreferrer">iconfont.cn</a>  下载图标的 svg 文件，放入<code>assets/svg</code>目录。在 <code>pubspec.yaml</code> 中配置静态资源：</p><div class="language-yaml"><span class="copy"></span><pre><code><span class="line"><span style="color:#F07178;">assets</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">assets/svg/</span></span>
<span class="line"></span></code></pre></div><p><strong>引入<code>flutter_svg</code>插件并使用：</strong></p><p>在项目终端执行指令 <code>flutter pub add flutter_svg</code> 下载插件。在页面中使用，以首页商品类型为例</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:flutter_svg/svg.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#82AAFF;">Padding</span><span style="color:#A6ACCD;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">    padding: const EdgeInsets</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">symmetric</span><span style="color:#A6ACCD;">(vertical: </span><span style="color:#F78C6C;">4</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// SvgPicture.asset 加载svg静态资源</span></span>
<span class="line"><span style="color:#A6ACCD;">    child: SvgPicture</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">asset</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">assets/svg/category_phone.svg</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> height: </span><span style="color:#F78C6C;">35</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">,</span></span>
<span class="line"></span></code></pre></div><h2 id="瀑布流布局" tabindex="-1">瀑布流布局 <a class="header-anchor" href="#瀑布流布局" aria-hidden="true">#</a></h2><p>商品列表项的高度可能不同，比如标题有一行也有两行。使用瀑布流布局，可完美协调各块的高度。</p><p><strong>如何挑选插件：</strong></p><p>在 flutter 插件市场 <a href="https://pub.flutter-io.cn/" target="_blank" rel="noopener noreferrer">pub.dev</a> 中搜索瀑布流布局插件，你找到的可能会很多，如何挑选最适合当前项目的呢？</p><p>比如我们项目开发环境是 <code>flutter v2.x</code> 版本，该版本最大的特点是支持 <code>Null Safety</code>。所以首先需要支持 <code>Null Safety</code>，然后挑选 <code>LIKES</code> 数量多并且符合瀑布流需求的插件。</p><p><strong>引入插件并使用：</strong></p><p>在项目终端执行指令 <code>flutter pub add flutter_staggered_grid_view</code> 下载插件。在项目中使用瀑布流布局编写商品列表组件</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#A6ACCD;">override</span></span>
<span class="line"><span style="color:#A6ACCD;">Widget </span><span style="color:#82AAFF;">build</span><span style="color:#A6ACCD;">(BuildContext context) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 使用插件的 MasonryGridView 类编写瀑布流布局</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">MasonryGridView</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">count</span><span style="color:#F07178;">(</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">controller</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">_scrollController</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">padding</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">widget</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">list</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">isNotEmpty</span></span>
<span class="line"><span style="color:#F07178;">          </span><span style="color:#89DDFF;">?</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">const</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">EdgeInsets</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">symmetric</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">vertical</span><span style="color:#F07178;">: </span><span style="color:#F78C6C;">12</span><span style="color:#F07178;">)</span></span>
<span class="line"><span style="color:#F07178;">          </span><span style="color:#89DDFF;">:</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">null,</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">shrinkWrap</span><span style="color:#F07178;">: </span><span style="color:#FF9CAC;">true</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">physics</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">const</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">NeverScrollableScrollPhysics</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">crossAxisCount</span><span style="color:#F07178;">: </span><span style="color:#F78C6C;">2</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#676E95;font-style:italic;">// 列的数量</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">mainAxisSpacing</span><span style="color:#F07178;">: </span><span style="color:#F78C6C;">20</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#676E95;font-style:italic;">// 主轴项之间的距离</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">crossAxisSpacing</span><span style="color:#F07178;">: </span><span style="color:#F78C6C;">12</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#676E95;font-style:italic;">// 交叉轴项之间的距离</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">itemCount</span><span style="color:#F07178;">: </span><span style="color:#A6ACCD;">widget</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">list</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">length</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">      </span><span style="color:#676E95;font-style:italic;">// 列表项生成器</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">itemBuilder</span><span style="color:#F07178;">: (</span><span style="color:#A6ACCD;">context</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">index</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">        return GoodsListItem</span><span style="color:#89DDFF;">(</span><span style="color:#F07178;">widget.list</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">index</span><span style="color:#89DDFF;">])</span><span style="color:#F07178;">;</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#89DDFF;">},</span></span>
<span class="line"><span style="color:#F07178;">    )</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h2 id="购物车功能" tabindex="-1">购物车功能 <a class="header-anchor" href="#购物车功能" aria-hidden="true">#</a></h2><p>购物车算是商城的核心功能之一，也有较复杂的逻辑。</p><ol><li>唯一性：同一件商品在颜色和尺寸等规格都相同的情况下，在购物车里才算同一件物品；</li><li>本地存储：购物车中的商品需要存储到本地，用户未结算关闭了应用，当下次打开应用时，购物车商品依然在；</li><li>全局状态：购物车需要全局读取，在商品详情页显示数量和加入购物车，在购物车中加减数量等。</li></ol><h3 id="创建购物车数据模型" tabindex="-1">创建购物车数据模型 <a class="header-anchor" href="#创建购物车数据模型" aria-hidden="true">#</a></h3><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9ec7b243395b4e8d84432d5455856084~tplv-k3u1fbpfcp-watermark.image?" alt="2081658201873_.pic.jpg"></p><p>购物车列表项 UI 如图所示，商品属性结合类型拟定 Json 数据：</p><div class="language-json"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">id</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">46</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">title</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">能方研影</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">price</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">61.96</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">cover</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">http://dummyimage.com/300x300/B9C6C3&amp;text=shopping%20mall</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">count</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">color</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">蓝色</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C792EA;">size</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">XS</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p>Json to Dart：前往<a href="https://app.quicktype.io/" target="_blank" rel="noopener noreferrer">quicktype</a>，将 Json 数据复制进去转换为 <code>Dart 数据模型</code>（当然也可以手动编写）</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">CartInfoModel</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F07178;">CartInfoModel</span><span style="color:#89DDFF;">({</span></span>
<span class="line"><span style="color:#A6ACCD;">    required this.id</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    required this.title</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    required this.price</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    required this.cover</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    required this.count</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    required this.color</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    required this.size</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">  int </span><span style="color:#F07178;">id</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">title</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  double </span><span style="color:#F07178;">price</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">cover</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  int </span><span style="color:#F07178;">count</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">color</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#FFCB6B;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">size</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h3 id="购物车状态管理" tabindex="-1">购物车状态管理 <a class="header-anchor" href="#购物车状态管理" aria-hidden="true">#</a></h3><p>项目使用 Flutter 官方推荐的 <a href="https://riverpod.dev/" target="_blank" rel="noopener noreferrer">riverpod</a> （一种反应式缓存和数据绑定框架） 进行购物车状态管理，让数据可以跨组件使用。</p><p><strong>引入插件并使用：</strong></p><p>在项目终端执行指令  <code>flutter pub add flutter_riverpod</code>  下载插件。创建 <code>lib/providers/cart_provider.dart</code>文件：</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:flutter_riverpod/flutter_riverpod.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> cartPrefsKey </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">cartList</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">CartNotifier</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ChangeNotifier</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 购物车列表</span></span>
<span class="line"><span style="color:#A6ACCD;">  final List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">CartInfoModel</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">cartList</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> &lt;</span><span style="color:#FFCB6B;">CartInfoModel</span><span style="color:#A6ACCD;">&gt;[]</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 总数量</span></span>
<span class="line"><span style="color:#A6ACCD;">  int </span><span style="color:#C792EA;">get</span><span style="color:#A6ACCD;"> totalCount </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">int</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">total</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#F07178;"> (</span><span style="color:#C792EA;">var</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">item</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">in</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartList</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">total</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">+=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">count</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">total</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 总价格</span></span>
<span class="line"><span style="color:#A6ACCD;">  double </span><span style="color:#C792EA;">get</span><span style="color:#A6ACCD;"> totalPrice </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">double</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">total</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#F07178;"> (</span><span style="color:#C792EA;">var</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">item</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">in</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartList</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">total</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">+=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">price</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">*</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">count</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">total</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 加入购物车</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 更新购物车数量</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 移除购物车指定项</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 查询/初始化购物车</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// ChangeNotifierProvider 用法</span></span>
<span class="line"><span style="color:#A6ACCD;">final cartProvider </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ChangeNotifierProvider</span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">CartNotifier</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">((ref) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  return </span><span style="color:#F07178;">CartNotifier</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span></code></pre></div><p><strong>注册全局状态：</strong></p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:flutter/material.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:flutter_riverpod/flutter_riverpod.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#A6ACCD;">() </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">runApp</span><span style="color:#F07178;">(</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// ProviderScope包裹入口组件，提供全局状态</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#82AAFF;">ProviderScope</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">child</span><span style="color:#F07178;">: </span><span style="color:#82AAFF;">MyApp</span><span style="color:#F07178;">())</span></span>
<span class="line"><span style="color:#F07178;">    )</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p><strong>在组件中读取：</strong></p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#676E95;font-style:italic;">// 读取购物车列表</span></span>
<span class="line"><span style="color:#A6ACCD;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">CartInfoModel</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> list </span><span style="color:#89DDFF;">=</span></span>
<span class="line"><span style="color:#A6ACCD;">    ref</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">watch</span><span style="color:#A6ACCD;">(cartProvider</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">select</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">value</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> value</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">cartList))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 读取结算价格</span></span>
<span class="line"><span style="color:#A6ACCD;">double totalPrice </span><span style="color:#89DDFF;">=</span></span>
<span class="line"><span style="color:#A6ACCD;">    ref</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">watch</span><span style="color:#A6ACCD;">(cartProvider</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">select</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">value</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> value</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">totalPrice))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 读取总数量</span></span>
<span class="line"><span style="color:#A6ACCD;">int totalCount </span><span style="color:#89DDFF;">=</span></span>
<span class="line"><span style="color:#A6ACCD;">    ref</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">watch</span><span style="color:#A6ACCD;">(cartProvider</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">select</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">value</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> value</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">totalCount))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 调用方法</span></span>
<span class="line"><span style="color:#A6ACCD;">ref</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">read</span><span style="color:#A6ACCD;">(cartProvider</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">notifier)</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">pushCart</span><span style="color:#A6ACCD;">(_cartInfo)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span></code></pre></div><h3 id="加入购物车" tabindex="-1">加入购物车 <a class="header-anchor" href="#加入购物车" aria-hidden="true">#</a></h3><p>首先查找同一规格的商品，有则更新数量，无则加入购物车。加入购物车的 <code>cartInfo</code> 需要<strong>深拷贝</strong>避免影响再次选择该商品<strong>其他规格</strong>的时候改动购物车数据。购物车数据改变后需要调用 <code>notifyListeners</code> 通知状态管理器更新状态。</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#676E95;font-style:italic;">/// 加入购物车</span></span>
<span class="line"><span style="color:#89DDFF;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">pushCart</span><span style="color:#A6ACCD;">(CartInfoModel cartInfo) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 查找 id/color/size 都相同的购物车商品</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">int</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartList</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">indexWhere</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">)</span><span style="color:#F07178;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&amp;&amp;</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">color</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">color</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&amp;&amp;</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">size</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">size</span><span style="color:#F07178;">))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&gt;</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">1</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">      </span><span style="color:#676E95;font-style:italic;">// 存在则数量+1</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">cartList</span><span style="color:#F07178;">[</span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;">]</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">count</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">+=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">count</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">}</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;font-style:italic;">else</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">      </span><span style="color:#676E95;font-style:italic;">// 不存在则添加（这里深拷贝cartInfo）</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">cartList</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">add</span><span style="color:#F07178;">(</span><span style="color:#82AAFF;">cartInfoModelFromJson</span><span style="color:#F07178;">(</span><span style="color:#82AAFF;">cartInfoModelToJson</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#F07178;">)))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 通知 CartNotifier 状态更新</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">notifyListeners</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 更新数据存储到本地</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">updCartPrefs</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">/// 更新购物车数量</span></span>
<span class="line"><span style="color:#89DDFF;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">updCartCount</span><span style="color:#A6ACCD;">(CartInfoModel cartInfo</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> int count) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 查找 id/color/size 都相同的购物车商品</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">int</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartList</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">indexWhere</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">)</span><span style="color:#F07178;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&amp;&amp;</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">color</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">color</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&amp;&amp;</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">size</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">size</span><span style="color:#F07178;">))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&gt;</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">1</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">      </span><span style="color:#676E95;font-style:italic;">// 存在则更新数量</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">cartList</span><span style="color:#F07178;">[</span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;">]</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">count</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">count</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">notifyListeners</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 更新数据存储到本地</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">updCartPrefs</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">/// 移除购物车指定商品</span></span>
<span class="line"><span style="color:#89DDFF;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">renmoveCart</span><span style="color:#A6ACCD;">(CartInfoModel cartInfo) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 查找 id/color/size 都相同的购物车商品</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">int</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartList</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">indexWhere</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">)</span><span style="color:#F07178;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&amp;&amp;</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">color</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">color</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&amp;&amp;</span></span>
<span class="line"><span style="color:#F07178;">        </span><span style="color:#A6ACCD;">item</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">size</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">==</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartInfo</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">size</span><span style="color:#F07178;">))</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&gt;</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">1</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">      </span><span style="color:#676E95;font-style:italic;">// 存在则移除该项</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">cartList</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">removeAt</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">idx</span><span style="color:#F07178;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">notifyListeners</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 更新数据存储到本地</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">updCartPrefs</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h3 id="购物车本地存储" tabindex="-1">购物车本地存储 <a class="header-anchor" href="#购物车本地存储" aria-hidden="true">#</a></h3><p><strong>需求：</strong> 商品加入到购物车，还未结算并退出应用，下次进来购物车商品还在。<br><strong>思路：</strong> 每次购物车数据有改动需要把数据更新到本地存储，下次打开 App 的时候从本地恢复数据到购物车。<br><strong>持久化存储：</strong> 项目使用 <code>shared_preferences</code> 插件进行本地持久化存储。以<code>key-value</code>的形式存储字符串到本地。</p><p><strong>引入插件并使用：</strong></p><p>在项目终端执行指令 <code>flutter pub add shared_preferences</code> 下载插件，在 <code>cart_provider.dart</code> 购物车状态管理类中使用：</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:shared_preferences/shared_preferences.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">CartNotifier</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ChangeNotifier</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  final List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">CartInfoModel</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">cartList</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> &lt;</span><span style="color:#FFCB6B;">CartInfoModel</span><span style="color:#A6ACCD;">&gt;[]</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">/// 更新购物车数据到本地持久化存储</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">updCartPrefs</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> async </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">final</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">prefs</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;font-style:italic;">await</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">SharedPreferences</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getInstance</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 购物车数据转为字符串</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#FFCB6B;">String</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartString</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">json</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">encode</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">cartList</span><span style="color:#F07178;">)</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">toString</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// `setString`存储到本地</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">prefs</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setString</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">cartList</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartString</span><span style="color:#F07178;">)</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">/// 查询/初始化 - 从本地读取数据恢复到购物车状态</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">  Future</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">CartInfoModel</span><span style="color:#89DDFF;">&gt;&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">getCartList</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> async </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 获取持久化实例</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">final</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">prefs</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;font-style:italic;">await</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">SharedPreferences</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getInstance</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 调用`getString`方法读取本地字符串数据</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#FFCB6B;">String</span><span style="color:#89DDFF;">?</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">_cartString</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">prefs</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getString</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">cartList</span><span style="color:#89DDFF;">&#39;</span><span style="color:#F07178;">);</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">cartList</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">clear</span><span style="color:#F07178;">();</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 字符串数据转换为`CartInfoModel`初始化到购物车列表</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">if</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">_cartString</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">!=</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">null</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">      List&lt;CartInfoModel&gt; </span><span style="color:#A6ACCD;">_list</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">cartInfoModelListFromJson</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">_cartString</span><span style="color:#F07178;">);</span></span>
<span class="line"><span style="color:#F07178;">      cartList.addAll(_list);</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#82AAFF;">notifyListeners</span><span style="color:#F07178;">();</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">return</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">cartList</span><span style="color:#F07178;">;</span></span>
<span class="line"><span style="color:#F07178;">  }</span></span>
<span class="line"><span style="color:#F07178;">}</span></span>
<span class="line"></span></code></pre></div><p><strong>初始化购物车：</strong></p><p>用户每次打开应用，恢复购物车数据：在入口文件 <code>main.dart</code> 中调用 <code>getCartList</code> 方法初始化购物车数据。</p><div class="language-ts"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">package:flutter_riverpod/flutter_riverpod.dart</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// flutter_riverpod 中的 `ConsumerStatefulWidget` 类</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 在状态组件中提供 ref 以使用状态管理器</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">MyApp</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ConsumerStatefulWidget</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  const </span><span style="color:#F07178;">MyApp</span><span style="color:#89DDFF;">({</span><span style="color:#A6ACCD;">Key? key</span><span style="color:#89DDFF;">})</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">super</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">key</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">key</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">@</span><span style="color:#A6ACCD;">override</span></span>
<span class="line"><span style="color:#A6ACCD;">  ConsumerState</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">MyApp</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">createState</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">_MyAppState</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">_MyAppState</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">extends</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ConsumerState</span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">MyApp</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">@</span><span style="color:#A6ACCD;">override</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">initState</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">super</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">initState</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 调用`getCartList`方法初始化购物车</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#A6ACCD;">ref</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">read</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">cartProvider</span><span style="color:#F07178;">)</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getCartList</span><span style="color:#F07178;">()</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">@</span><span style="color:#A6ACCD;">override</span></span>
<span class="line"><span style="color:#A6ACCD;">  Widget </span><span style="color:#F07178;">build</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">BuildContext context</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">MaterialApp</span><span style="color:#F07178;">(</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">title</span><span style="color:#F07178;">: </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">青山商城</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">    )</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h2 id="项目-app-打包" tabindex="-1">项目 App 打包 <a class="header-anchor" href="#项目-app-打包" aria-hidden="true">#</a></h2><p>代码写的好不如产品包装的好，打包也是至关重要的一步。由于设备有限，项目只亲测了安卓 apk 打包，其他终端的打包方法参考 <a href="https://flutter.cn/docs/deployment/android" target="_blank" rel="noopener noreferrer">官方打包部署</a></p><h3 id="打包-apk-指令" tabindex="-1">打包 apk 指令 <a class="header-anchor" href="#打包-apk-指令" aria-hidden="true">#</a></h3><p>项目终端执行指令，等待打包结束后提示 apk 位置。<code>--split-per-abi</code> 按 abi 拆分 apk，减小包体积</p><div class="language-"><span class="copy"></span><pre><code><span class="line"><span style="color:#A6ACCD;">flutter build apk --split-per-abi</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span></code></pre></div><h3 id="应用名称修改" tabindex="-1">应用名称修改 <a class="header-anchor" href="#应用名称修改" aria-hidden="true">#</a></h3><ul><li><p>Android：在 <code>android/app/src/main/AndroidManifest.xml</code> 中修改<code>android:label=&quot;应用名称&quot;</code></p></li><li><p>iOS：在 <code>ios/Runner/Info.plist</code> 中修改 <code>CFBundleName</code> 对应的 Value</p></li></ul><h3 id="应用图标修改" tabindex="-1">应用图标修改 <a class="header-anchor" href="#应用图标修改" aria-hidden="true">#</a></h3><ul><li><p>Android： 在 <code>android/app/src/res/mipmap</code> 文件夹中替换相应图片</p></li><li><p>iOS：在 <code>ios/Runner/Assets.xcassets/AppIcon.appiconset</code> 文件夹中替换相应尺寸的图片。如果使用不同的文件名，还需更新同一目录中的 <code>Contents.json</code> 文件中的 <code>filename</code></p></li></ul><h3 id="启动页图片" tabindex="-1">启动页图片 <a class="header-anchor" href="#启动页图片" aria-hidden="true">#</a></h3><ul><li><p>Android：在 <code>android/app/src/res/drawable/launch_background.xml</code> 文件实现自定义启动界面，这里的是 xml 语法。也可以使用默认模版，在 <code>android/app/src/res/mipmap</code> 相应尺寸文件下放入启动图片<code>launch_image</code>：</p><div class="language-xml"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">item</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">bitmap</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#C792EA;">android</span><span style="color:#89DDFF;">:</span><span style="color:#C792EA;">gravity</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">center</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#C792EA;">android</span><span style="color:#89DDFF;">:</span><span style="color:#C792EA;">src</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">@mipmap/launch_image</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;"> /&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">item</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"></span></code></pre></div></li><li><p>iOS：在 <code>ios/Runner/Assets.xcassets/LaunchImage.imageset</code> 文件夹中替换相应尺寸的图片。如果使用不同的文件名，还需更新同一目录中的 <code>Contents.json</code> 文件中的 <code>filename</code></p></li></ul><h3 id="安卓-app-签名" tabindex="-1">安卓 App 签名 <a class="header-anchor" href="#安卓-app-签名" aria-hidden="true">#</a></h3><p>若需要将 apk 发布到各大应用市场，比如应用宝等，需要对 app 进行签名。</p><ol><li>终端执行下方指令获取秘钥文件，文件默认在当前目录，将生成的 <code>key.jks</code> 复制到项目的 <code>android/app/</code> 目录下：</li></ol><div class="language-sh"><span class="copy"></span><pre><code><span class="line"><span style="color:#A6ACCD;">keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key</span></span>
<span class="line"></span></code></pre></div><ol start="2"><li>修改 <code>android/key.properties</code> 文件（没有则新建）</li></ol><div class="language-xml"><span class="copy"></span><pre><code><span class="line"><span style="color:#A6ACCD;">storePassword=</span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">上一步骤中的密码</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">keyPassword=</span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">上一步骤中的密码</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">keyAlias=key</span></span>
<span class="line"><span style="color:#A6ACCD;">storeFile=key.jks</span></span>
<span class="line"></span></code></pre></div><ol start="3"><li><code>android/app/build.gradle</code> 中，<code>android</code> 代码块之前新增内容</li></ol><div class="language-java"><span class="copy"></span><pre><code><span class="line"><span style="color:#676E95;font-style:italic;">// 新增的内容</span></span>
<span class="line"><span style="color:#A6ACCD;">def keystoreProperties </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Properties</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">def keystorePropertiesFile </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> rootProject</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">file</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">key.properties</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">keystorePropertiesFile</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">exists</span><span style="color:#89DDFF;">())</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    keystoreProperties</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">load</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">FileInputStream</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">keystorePropertiesFile</span><span style="color:#89DDFF;">))</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// android 代码块</span></span>
<span class="line"><span style="color:#A6ACCD;">android </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">...</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p>修改 <code>signingConfigs</code> 和 <code>buildTypes</code> 代码块：</p><div class="language-java"><span class="copy"></span><pre><code><span class="line"><span style="color:#676E95;font-style:italic;">// 新增内容</span></span>
<span class="line"><span style="color:#A6ACCD;">signingConfigs </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    release </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        keyAlias keystoreProperties</span><span style="color:#89DDFF;">[</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">keyAlias</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">]</span></span>
<span class="line"><span style="color:#A6ACCD;">        keyPassword keystoreProperties</span><span style="color:#89DDFF;">[</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">keyPassword</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">]</span></span>
<span class="line"><span style="color:#A6ACCD;">        storeFile keystoreProperties</span><span style="color:#89DDFF;">[</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">storeFile</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">]</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">?</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">file</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">keystoreProperties</span><span style="color:#89DDFF;">[</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">storeFile</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">])</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">null</span></span>
<span class="line"><span style="color:#A6ACCD;">        storePassword keystoreProperties</span><span style="color:#89DDFF;">[</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">storePassword</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">]</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 替换的内容</span></span>
<span class="line"><span style="color:#A6ACCD;">buildTypes </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    release </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        signingConfig signingConfigs</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">release</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h3 id="其他打包注意事项" tabindex="-1">其他打包注意事项 <a class="header-anchor" href="#其他打包注意事项" aria-hidden="true">#</a></h3><p><strong>apk 打包后网络图片不显示：</strong></p><p>在 <code>android/app/src/profile/AndroidManifest.xml</code> 中允许网络权限（若无效则需要在 <code>android/app/src/main/AndroidManifest.xml</code>也配置一份）：</p><div class="language-xml"><span class="copy"></span><pre><code><span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">uses-permission</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">android</span><span style="color:#89DDFF;">:</span><span style="color:#C792EA;">name</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">android.permission.INTERNET</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">/&gt;</span></span>
<span class="line"></span></code></pre></div><p><strong>空安全错误（flutter v2）：</strong></p><p>如果你使用的是 <code>flutter v2</code> 版本或者更高版本，运行或打包时报空安全错误(null safety)。是因为你在项目中的部分写法或者<strong>插件</strong>不支持空安全规范，建议替换支持 <code>Null Safety</code> 的插件。或者使用以下指令 <code>--no-sound-null-safety</code> 跳过 <code>Null Safety</code> 检测。</p><div class="language-"><span class="copy"></span><pre><code><span class="line"><span style="color:#A6ACCD;">flutter build apk --no-sound-null-safety</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span></code></pre></div><h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-hidden="true">#</a></h2><p>可以看到，文章开头的 <code>Flutter商城APP实战知识点图</code>，项目知识点大大小小有几十个，因为精力有限，抽取了几个主要知识点讲解。如果大家想要一起学习图中 <strong>其他的知识点</strong>，可以在评论区 <strong>留言 💬</strong>， 每周会抽取时间 <strong>更新文章</strong>。</p><p>博观而约取，厚积而薄发<br> 记得 👍 点赞 + ❤️ 收藏，➕ 关注不迷路</p><p>项目 github：<a href="https://github.com/jsdawn/shopping_mall" target="_blank" rel="noopener noreferrer">https://github.com/jsdawn/shopping_mall</a></p><blockquote><p><strong>转载声明</strong>： 请注明作者，注明原文链接，有疑问致邮 <a href="mailto:kingwyh1993@163.com">kingwyh1993@163.com</a></p></blockquote></div></div></main><footer class="VPDocFooter" data-v-594e0a10 data-v-a2c60774><div class="edit-info" data-v-a2c60774><div class="edit-link" data-v-a2c60774><a class="VPLink link edit-link-button" href="https://github.com/jsdawn/note/edit/master/docs/guide/flutter-mall.md" target="_blank" rel="noopener noreferrer" data-v-a2c60774 data-v-32407edf><!--[--><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="edit-link-icon" data-v-a2c60774><path d="M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"></path><path d="M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"></path></svg> Edit this page on github<!--]--><!----></a></div><div class="last-updated" data-v-a2c60774><p class="VPLastUpdated" data-v-a2c60774 data-v-16d7c010>Updated Date: <time datatime="2022-07-27T04:06:36.000Z" data-v-16d7c010></time></p></div></div><div class="prev-next" data-v-a2c60774><div class="pager" data-v-a2c60774><a class="pager-link prev" href="/note/guide/vue3-i18n.html" data-v-a2c60774><span class="desc" data-v-a2c60774>Previous page</span><span class="title" data-v-a2c60774>vue3 国际化(i18n)</span></a></div><div class="has-prev pager" data-v-a2c60774><a class="pager-link next" href="/note/guide/flutter-weiget.html" data-v-a2c60774><span class="desc" data-v-a2c60774>Next page</span><span class="title" data-v-a2c60774>Flutter 布局相关</span></a></div></div></footer><!--[--><!--]--></div></div></div></div></div><footer class="VPFooter has-sidebar" data-v-c66ff1de data-v-758a7cec><div class="container" data-v-758a7cec><p class="message" data-v-758a7cec>Released under the Apache License 2.0 License.</p><p class="copyright" data-v-758a7cec>Copyright © 2022-present 青山依旧</p></div></footer><!--[--><!--]--></div></div>
    <script>__VP_HASH_MAP__ = JSON.parse("{\"components_index.md\":\"c71eaabc\",\"feelings_a-upload.md\":\"c24f19c1\",\"feelings_echarts-wordcloud.md\":\"49f7ba66\",\"feelings_el-date-picker.md\":\"f1052dce\",\"feelings_js-lazy-load.md\":\"866a6573\",\"feelings_vue3-waterfall-flow.md\":\"d38323f6\",\"guide_docker-compose.md\":\"61ab6185\",\"guide_docker-github.md\":\"38b70b85\",\"guide_flutter-const.md\":\"db61f59e\",\"guide_flutter-mall.md\":\"053d420a\",\"guide_flutter-weiget.md\":\"35457cf1\",\"guide_vue3-i18n.md\":\"ef8b8776\",\"guide_vue3-tinymce.md\":\"d6c4784e\",\"guide_vue3-upgrade.md\":\"c0d0a296\",\"index.md\":\"42f45fbc\",\"node_git-npm.md\":\"94662dbd\",\"tale_hexo-create.md\":\"9496a4e9\",\"tale_hualang.md\":\"3079cb8f\",\"tale_p1-zongjie.md\":\"50509583\",\"widgets_index.md\":\"f7612fd9\"}")</script>
    <script type="module" async src="/note/assets/app.6518bc14.js"></script>
    
  </body>
</html>